Because sharing data is caring
reactableGreg Lin, the author of reactable recently added support for crosstalk to the package! This is great, because it allows you to do many things:
We’ll continue on our example use-cases of interactive tables w/ reactable, but now add in a bit of crosstalk to show how this can be useful.
My previous posts on:
- reactable - How to guide for interactive tables
- gt - How to guide for static tables
Like most examples, we’ll need additional data for this example. ESPN was kind enough to give us a hot-take to start off with:
Pretty striking huh? Of these tops QBs, Rodgers has almost no 1st round receivers on his team (or TE/RB). But what about the rest of the story? WHO does he throw TDs to?
Let’s take a look at the data, courtesy of pro-football-reference.com. The following 3 data sources got me these results (and the raw csv output can be found here)
Let’s read in our data - however note that this is long-format, so it’s not ideal for our summary table. We’ll tidyr::pivot_wider() in the next step and use some new dplyr features!
However we can see that we have:
- a passer
- a draft round variable (round)
- total touchdowns thrown (n)
- total touchdowns thrown (total)
- the ratio of touchdowns thrown per round relative to the total touchdowns normalized to each passer (ratio).
Rows: 96
Columns: 5
$ passer <chr> "Aaron Rodgers", "Aaron Rodgers", "Aaron Rodgers", "…
$ rnd <fct> Rnd 1, Rnd 2, Rnd 3, Rnd 4, Rnd 5, Rnd 6, Rnd 7, Und…
$ n <int> 1, 191, 82, 5, 23, 6, 24, 32, 69, 31, 101, 35, 17, 7…
$ total <int> 364, 364, 364, 364, 364, 364, 364, 364, 365, 365, 36…
$ ratio <dbl> 0.002747253, 0.524725275, 0.225274725, 0.013736264, …
We’ll use tidyr::unnest_wider() to widen this data where each passer/QB get their own row and the round of their receivers get their own column. This is ready to go into it’s own table!
Rows: 12
Columns: 10
$ passer <chr> "Aaron Rodgers", "Ben Roethlisberger", "Carson P…
$ `Rnd 1` <dbl> 0.003, 0.189, 0.196, 0.190, 0.358, 0.514, 0.406,…
$ `Rnd 2` <dbl> 0.525, 0.085, 0.227, 0.112, 0.212, 0.000, 0.196,…
$ `Rnd 3` <dbl> 0.225, 0.277, 0.221, 0.152, 0.098, 0.168, 0.100,…
$ `Rnd 4` <dbl> 0.014, 0.096, 0.012, 0.042, 0.040, 0.112, 0.025,…
$ `Rnd 5` <dbl> 0.063, 0.047, 0.081, 0.029, 0.064, 0.022, 0.085,…
$ `Rnd 6` <dbl> 0.016, 0.211, 0.025, 0.004, 0.042, 0.012, 0.075,…
$ `Rnd 7` <dbl> 0.066, 0.008, 0.146, 0.141, 0.019, 0.028, 0.000,…
$ `Rnds 1-3` <dbl> 0.753, 0.551, 0.644, 0.454, 0.668, 0.682, 0.702,…
$ Undrafted <dbl> 0.088, 0.088, 0.090, 0.331, 0.167, 0.143, 0.114,…
We’ll create a color palette function using viridis.
Now we can create the table - here is the basic table that gets us most of the way!
I’ve added comments to the code so you can see what the arguments do.
We’ll add in a few fonts (locally this time, instead of from Google), add some additional styling, and then I’m happy with this as an output table. Feel free to compare to what round of receivers QBs are throwing to, and you have filtering via the search bar.
While Aaron Rodgers hasn’t thrown hardly any passes to 1st Rounders - he actually leads this group in passes thrown to 1st, 2nd and 3rd rounders! The story can change a bit based on how you choose the data/cutoff. However, there’s a bit more to this story that is missing from the below table.
This table shows us a lot - Rodgers actually has thrown the most passes to 2nd Round receivers of any of these QBs, and the most passes to 1st, 2nd or 3rd Round receivers, along with the fewest passes to undrafted receivers! Not quite, the same storyline. An additional consideration - where in the overall draft were the players catching passes taken?
crosstalkFor a bit of a deeper dive, we can go up one level and rather than focusing on just the QBs, we can look at the QBs and stats on who has caught their passes. We’ll also use crosstalk to provide some rich interactive filtering purely on the client side, this means all done in browser as opposed to needing a Shiny runtime/server backend.
Our first step is to load the crosstalk library and define our SharedData - this is how crosstalk knows how to relate the interaction together - the data is added to a new object that is shared across various resources. For simple examples, once this is defined you can just use the newly defined wr_data just as you would normal dataframes.
Let’s try it out!
For this first table, we’ll just stay simple, with the major addition being that we’re adding a longer description to the pos_rank column which is a tricky definition. You can now hover over the column label to get a tooltip. I’ve also done something relatively easy to do, but that adds some complexity. I’ve grouped each QB so that their WRs display as sub-tables within the parent row.
This is done with groupBy inside reactable, and you then define what type of aggregation you want inside the colDef. I have unique teams for team, count of total receivers for receiver, sum of total TDs for TDs caught, unique position draft rank for position draft, and frequency for draft round. Note that you can still interact with this table normally, and that we haven’t used any special crosstalk features yet. Also: side note - I’ve replaced undrafted players with a pos_rank of 44. This is the max of pos_rank for drafted players + 1. Deal with it! (#sorry).
crosstalk add onsSo that table is interactive, but it’s all sorting. What about all the special crosstalk features?
We can align some filters and the table together with bootstrap columns (similar to setting up shiny structure). These come along with loading crosstalk. Note that this is because I’m working in a traditional RMarkdown, and if I were using something like flexdashboard, it has alternate and more robust methods of aligning various plots, tables, control boxes, etc.
That’s it! No setting up shiny, no server, you get all this filtering for free! Now there’s still a spectrum where shiny adds a LOT of value - namely, I want to do more custom work and need to execute additional R code!
crosstalk featuresNow while the table-level filtering is nice, you can also communicate between various crosstalk enabled widgets, including plotly graphs, reactable, DT, and leaflet.
This is a rather minimal example as far as plotly, but you can see some of the interaction across the 3 levels (filters, plotly, reactable)
This can be further extended to shiny - where some of the interaction can happen at the level of the client (ie JavaScript) and other portions can be pushed down to R (shiny).
That’s out of scope for today, but reading material at:
- Free plotly R book
- crosstalk + Shiny website